//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
// 
// You should have received a copy of the GNU Lesser General Public License
// along with this program.  If not, see http://www.gnu.org/licenses/.
// 

#include "StemNetStimulusBrain.h"

#include "BasicBattery.h"

#include "IPvXAddressResolver.h"
#include "UDPControlInfo_m.h"

Define_Module(StemNetStimulusBrain);

StemNetStimulusBrain::StemNetStimulusBrain() {
}

StemNetStimulusBrain::~StemNetStimulusBrain() {
    while (!queueForwardMsg.empty()) {
        delete(queueForwardMsg.pop());
    }

    cancelAndDelete(msgForwardDelay);
    cancelAndDelete(msgElectionDelay);
    cancelAndDelete(msgDecisionStep);
    cancelAndDelete(msgTrafficStep);
    cancelAndDelete(msgUpdateDBStep);
    cancelAndDelete(msgStimulusBroadcastStep);
    cancelAndDelete(msgGatewayBroadcastStep);
    cancelAndDelete(endElectionPhase);
    cancelAndDelete(msgProtocolTimer);
    cancelAndDelete(msgStatisticalStep);

    delete (byteGenerated);
    delete (myActualRole);
    delete (isGatewayRole);
    delete (isTransitRole);
    delete (isStubRole);
    delete (actualEnergyVal);
    delete (pktGenerated);
}

void StemNetStimulusBrain::initialize(int stage)
{
    if(stage == 0) {

        dbIn = findGate("dbIn");
        dbOut = findGate("dbOut");
        wifiIn = findGate("udpIn");
        wifiOut = findGate("udpOut");
        dummy3GIn = findGate("dummy3GIn");
        dummy3GOut = findGate("dummy3GOut");

        db = check_and_cast<KnowledgeRepository *>(this->getParentModule()->getSubmodule("db"));
        module3g = check_and_cast<Emulate3G *>(this->getParentModule()->getSubmodule("emulate3G"));
        myApplicationAddress = this->getParentModule()->getParentModule()->getIndex();

        decisionStepTime = par("decisionStepTime");
        trafficStepTime = par("trafficStepTime");
        stimulusBroadcastStepTime = par("stimulusBroadcastStepTime");
        gatewayBroadcastStepTime = par("gatewayBroadcastStepTime");
        maxStimulusStep = par("maxStimulusStep");
        localPortStim = par("localPortStim");
        destPortStim = par("destPortStim");
        responseFunctionExponent = par("responseFunctionExponent");
        totalResponseFunctionExponent = par("totalResponseFunctionExponent");
        trafficMessageLength = par("messageStepLength");
        maxForwardDelay = par("maxForwardDelay");
        timeElectionPhase = par("timeElectionPhase");
        gatewayProbability = par("gatewayProbability");
        module3GProbability = par("module3GProbability");

        sendStimulus = par("sendStimulus").boolValue();
        sendGatewayMsg = par("sendGatewayMsg").boolValue();
        sendPeriodicGatewayMsg = par("sendPeriodicGatewayMsg").boolValue();

        becomeGWifAlone = par("becomeGWifAlone").boolValue();
        protocolTimer = par("protocolTimer");
        stimulusOnRouting = par("stimulusOnRouting").boolValue();;
        probabilisticValue = par("probabilisticValue").doubleValue();
        distanceToGW = par("distanceToGW");

        protocolType = STEMNET;
        std::string protocolTypeStr = par("protocolType").stdstringValue();
        if(protocolTypeStr.compare("stemnet") == 0) {
            protocolType = STEMNET;
        }
        else if(protocolTypeStr. compare("bestGW") == 0) {
            protocolType = BEST_GW;
        }
        else if(protocolTypeStr. compare("probabilistic") == 0) {
            protocolType = PROBABILISTIC;
        }
        else if(protocolTypeStr. compare("distance") == 0) {
            protocolType = DISTANCE;
        }

        if (timeElectionPhase >= stimulusBroadcastStepTime) {
            timeElectionPhase = stimulusBroadcastStepTime * 0.9;
        }

        electionType = SINGLE;
        std::string electionTypeStr = par("electionType").stdstringValue();
        if(electionTypeStr.compare("Single") == 0) {
            electionType = SINGLE;
        }
        else if(electionTypeStr. compare("Multiple") == 0) {
            electionType = MULTIPLE;
        }

        EV << "sendStimulus: " << sendStimulus << endl
                << " sendGatewayMsg: " << sendGatewayMsg << endl
                << " electionType: " << electionType << endl
                << " sendPeriodicGatewayMsg: " << sendPeriodicGatewayMsg << endl
                << " gatewayBroadcastStepTime: " << gatewayBroadcastStepTime << endl;

        alpha = par("alpha_");
        beta = par("beta_");
        gamma = par("gamma_");
        delta = par("delta_");
        eta = par("eta_");

        list_pkt_sent.clear();
        forwardMap.clear();
        gatewayList.clear();
        transitList.clear();
        stubList.clear();

        stimSrc = IPv4Address::ALLONES_ADDRESS;
        newlyElectedGW = 0;
        CcellSrcStimulus = 0;
        numberOfNewGW = 0;

        forwardable_id = 0;

        queueForwardMsg.clear();
        queueForwardMsg.setName("msgForward");

        module3GPresent = false;
        if (dblrand() <= module3GProbability) {
            module3GPresent = true;
        }

        msgDecisionStep = new cMessage("decisionStepMsg");
        //scheduleAt(simTime() + fabs(normal(decisionStepTime, 0.1)), msgDecisionStep);
        scheduleAt(simTime() + dblrand() * decisionStepTime, msgDecisionStep);

        msgTrafficStep = new cMessage("trafficStepMsg");
        //scheduleAt(simTime() + fabs(normal(trafficStepTime, 0.1)), msgTrafficStep);
        scheduleAt(simTime()
                + trafficStepTime
                + dblrand()*trafficStepTime
                //+ fabs(normal(60, 10)),
                + fabs(normal(10, 3)),
                msgTrafficStep);

        msgStimulusBroadcastStep = new cMessage("stimulusBroadcastStepMsg");
        //scheduleAt(simTime() + fabs(normal(stimulusBroadcastStepTime, 0.1)), msgStimulusBroadcastStep);
        //scheduleAt(simTime() + fabs(normal(stimulusBroadcastStepTime, (stimulusBroadcastStepTime / 10.0))), msgStimulusBroadcastStep);
        scheduleAt(simTime() + stimulusBroadcastStepTime + (dblrand() * stimulusBroadcastStepTime), msgStimulusBroadcastStep);

        msgGatewayBroadcastStep = new cMessage("msgGatewayBroadcastStep");
        //scheduleAt(simTime() + fabs(normal(gatewayBroadcastStepTime, (gatewayBroadcastStepTime / 10.0))), msgGatewayBroadcastStep);
        scheduleAt(simTime() + gatewayBroadcastStepTime + (dblrand() * gatewayBroadcastStepTime), msgGatewayBroadcastStep);

        msgUpdateDBStep = new cMessage("updateDBStepMsg");
        scheduleAt(simTime() + fabs(normal(1, 0.1)), msgUpdateDBStep);

        msgForwardDelay = new cMessage("stimulusForwardDelay");
        msgElectionDelay = new cMessage("electionDelay");
        endElectionPhase = new cMessage("endElectionPhase");

        msgProtocolTimer = new cMessage("ProtocolTimerStep");
        scheduleAt(simTime() + protocolTimer + (dblrand() * protocolTimer), msgProtocolTimer);
        //scheduleAt(simTime() + fabs(normal(protocolTimer, (protocolTimer / 10.0))), msgUpdateDBStep);

        msgStatisticalStep = new cMessage("StatisticalStep");
        scheduleAt(simTime() + 0.5, msgStatisticalStep);

        // stats initialization
        byteGenerated = new cOutVector("Byte generated");

        countPktGenerated = 0;
        totalPktGenerated = 0;
        pktGenerated = new cOutVector("Packets generated");

        myActualRole = new cOutVector("Role played");
        isGatewayRole = new cOutVector("Is Gateway");
        isTransitRole = new cOutVector("Is Transit");
        isStubRole = new cOutVector("Is Stub");

        actualEnergyVal = new cOutVector("Residual Capacity");
    }
    else if (stage == 3) {

        //initialize the rule
        changeRole(STEM_TRANSIT);

        // init DB variables
        db->setAlphaVar(alpha);
        db->setBetaVar(beta);
        db->setStimulusIntervalTime(stimulusBroadcastStepTime);
        db->setGatewayIntervalTime(gatewayBroadcastStepTime);
        db->setResponseExponentVar(responseFunctionExponent);
        db->setTotalResponseExponentVar(totalResponseFunctionExponent);
        db->setStimulusOnRouting(stimulusOnRouting);

        char buffName[64];
        snprintf(buffName, sizeof(buffName), "%s[%d]", this->getParentModule()->getParentModule()->getName(), myApplicationAddress);
        IPvXAddressResolver().tryResolve(buffName, myIP);
        EV << "I'm " << buffName << "; my IP is: " << myIP << endl;

        //initialize stimulus socket
        socketStimulus.setOutputGate(gate("udpOut"));
        socketStimulus.bind(localPortStim);
        socketStimulus.setTimeToLive(maxStimulusStep);  // set the time to live
        socketStimulus.setBroadcast(true);              // enable receiving broadcast packets

        this->getParentModule()->getParentModule()->getDisplayString().setTagArg("t", 0, myIP.str().c_str());

        //WATCH_LIST(table_stimulus);
        EV << "Appl addr: " << myApplicationAddress << " --> IP addr: " << myIP << endl;

        //EV << "MY APPLADDR: " << myApplicationAddress << endl;
        /*if(myApplicationAddress == 3) {
            changeRole(STEM_GATEWAY);
            //generateGatewayON(IPv4Address::ALLONES_ADDRESS);
            generateGatewayON(myIP);
        }*/
        if(myApplicationAddress == 0){
            msgDebugStep = new cMessage("msgDebugStep");
            scheduleAt(simTime() + 0.01, msgDebugStep);
        }


        //check if there is at least one node with the 3G module
        if(myApplicationAddress == 0){
            check3Gmodule();
        }
    }

    UDPBasicApp::initialize(stage);
}

void StemNetStimulusBrain::check3Gmodule(void) {
    bool atLeastOne = false;

    int nNodes = this->getParentModule()->getParentModule()->getParentModule()->par("numHosts");
    for (int i = 0; i < nNodes; i++) {
        StemNetStimulusBrain *ssb = check_and_cast<StemNetStimulusBrain *>(this->getParentModule()->getParentModule()->getParentModule()->getSubmodule("host", i)->getSubmodule("udpApp", 0)->getSubmodule("brain"));

        if(ssb->getModule3GPresent()) {
            atLeastOne = true;
            break;
        }
    }

    if(!atLeastOne) {
        // nobody with the 3Gmodule... getting one for me :)
        module3GPresent = true;
    }
}

void StemNetStimulusBrain::changeRole(STEM_ROLE_t newRole) {
    STEM_ROLE_t oldRule = db->getActualRule();

    //changing the visualized icon
    switch(newRole){
    case STEM_STUB:
        if(oldRule == STEM_GATEWAY) {
            //generateGateway(false);
        }

        module3g->switchOFF();

        break;

    case STEM_TRANSIT:
        if(oldRule == STEM_GATEWAY) {
            //generateGateway(false);
        }

        module3g->switchOFF();

        break;

    case STEM_GATEWAY:
        //generateGateway(true);
        module3g->switchON();
        break;
    }


    db->setActualRule(newRole);
}

void StemNetStimulusBrain::handleMessageWhenUp(cMessage *msg)
{
    //UDPBasicApp::handleMessageWhenUp(msg);

    if (msg->isSelfMessage()) {
        handleSelfMsg(msg);
    }
    else if (msg->getArrivalGateId() == dbIn) {
        delete(msg);
    }
    else if (msg->getArrivalGateId() == wifiIn) {
        handleWifiMsg(msg);
    }
    else if (msg->getArrivalGateId() == dummy3GIn) {
        delete(msg);
    }
    else {
        error("Unknown message received: %s", msg->getFullName());
        delete(msg);
    }
}


void StemNetStimulusBrain::handleSelfMsg(cMessage *msg) {
    if(msg == selfMsg) {    // inherit from UDP application

        switch (selfMsg->getKind()) {
            case START: processStart(); break;
            case SEND:  processSend(); break;
            case STOP:  processStop(); break;
            default: throw cRuntimeError("Invalid kind %d in self message", (int)selfMsg->getKind());
        }
    }
    else if (msg == msgStatisticalStep) {

        makePeriodocStats();

        scheduleAt(simTime() + 0.5, msgStatisticalStep);
    }
    else if (msg == msgDebugStep) {
        /*FILE *fDebug;

        fDebug = fopen("debug.txt", "w");
        if(fDebug != NULL) {
            char buff[32];
            int nChar;

            nChar = snprintf(buff, sizeof(buff), "%s", simTime().str().c_str());

            fwrite(buff, 1, nChar, fDebug);
            fclose(fDebug);
        }

        scheduleAt(simTime() + 0.005, msgDebugStep);*/
    }
    else if (msg == msgElectionDelay) {
        reactToDeferTimeExpired();
    }
    else if (msg == msgProtocolTimer) {
        switch(protocolType){
        case BEST_GW:
            chooseAndElectTheBestGW();
            break;

        case PROBABILISTIC:
            probabilisticBecomeGW();
            break;

        case DISTANCE:
            distanceBecomeGW();
            break;

        case STEMNET:
        default:
            break;
        }

        scheduleAt(simTime() + fabs(normal(protocolTimer, (protocolTimer / 5.0))), msgProtocolTimer);
    }
    else if(msg == msgUpdateDBStep) {

        // DEPRECATED
        //updateTable();
        //scheduleAt(simTime() + fabs(normal(1, 0.1)), msgUpdateDBStep);
    }
    else if(msg == msgDecisionStep) {

        // DEPRECATED
        //takeRoleDecision();
        //scheduleAt(simTime() + fabs(normal(decisionStepTime, (decisionStepTime / 10.0))), msgDecisionStep);
    }
    else if(msg == msgStimulusBroadcastStep) {

        if ((protocolType == STEMNET) && (sendStimulus)) {

            //checkStayGatewayOrLeave();

            generateStimulus();

            //scheduleAt(simTime() + fabs(normal(stimulusBroadcastStepTime, (stimulusBroadcastStepTime / 10.0))), msgStimulusBroadcastStep);
            scheduleAt(simTime() + stimulusBroadcastStepTime + (dblrand() * stimulusBroadcastStepTime), msgStimulusBroadcastStep);

            // set the end of the election phase
            scheduleAt(simTime() + timeElectionPhase, endElectionPhase);

            EV << simTime() << " Scheduling msgStimulusBroadcastStep at: " << msgStimulusBroadcastStep->getArrivalTime() << endl;
        }
    }
    else if(msg == endElectionPhase) {
        checkStayGatewayOrLeave();
    }
    else if(msg == msgGatewayBroadcastStep) {

        if (sendPeriodicGatewayMsg) {
            if (db->getActualRule() == STEM_GATEWAY) {
                generateGatewayON(IPv4Address::ALLONES_ADDRESS, false);
            }
            else {
                generateGatewayOFF();
            }

            scheduleAt(simTime() + fabs(normal(gatewayBroadcastStepTime, (gatewayBroadcastStepTime / 5.0))), msgGatewayBroadcastStep);

            EV << simTime() << " Scheduling msgGatewayBroadcastStep at: " << msgGatewayBroadcastStep->getArrivalTime() << endl;
        }
    }
    else if(msg == msgTrafficStep) {

        //if(myApplicationAddress == 0) {
            generateTraffic();

            scheduleAt(simTime() + fabs(normal(trafficStepTime, (trafficStepTime / 10.0))), msgTrafficStep);

            EV << simTime() << " Scheduling msgTrafficStep at: " << msgTrafficStep->getArrivalTime() << endl;
        //}
    }
    else if (msg == msgForwardDelay) {

        if(!queueForwardMsg.empty()) {
            ForwardableMSG *forwardMsg = check_and_cast<ForwardableMSG *>(queueForwardMsg.pop());
            socketStimulus.sendTo(forwardMsg, IPv4Address::ALLONES_ADDRESS, destPortStim);
        }

        if(!queueForwardMsg.empty()) {
            scheduleAt(simTime() + (dblrand() * maxForwardDelay), msgForwardDelay);
        }
    }
    else {
        error("Unknown self message received: %s", msg->getFullName());
        delete(msg);
    }

    if (ev.isGUI())
    {
        char buf[40];
        sprintf(buf, "rcvd: %d pks\nsent: %d pks", numReceived, numSent);
        getDisplayString().setTagArg("t", 0, buf);
    }
}

void StemNetStimulusBrain::checkStayGatewayOrLeave(void) {
    if (db->getActualRule() == STEM_GATEWAY) {
        double gatewayCost;
        double randVal = dblrand();

        if (numberOfNewGW == 0) {
            gatewayCost = (beta * db->getCongestionFactorCell()) - (alpha * (1 - db->getEnergyFactor()));   // TODO che fare? è giusto?
        }
        else {
            gatewayCost = (beta * ((db->getCongestionFactorCell() / numberOfNewGW) - (db->getCongestionFactorCell() / (numberOfNewGW + 1)))) -
                            (alpha * (1 - db->getEnergyFactor()));
        }

        if (    ((gatewayCost > 0) && (randVal >= gatewayProbability)) ||
                ((gatewayCost <= 0) && (randVal < gatewayProbability)) ){
            // leave from GATEWAY
            changeRole(STEM_TRANSIT);

            generateGatewayOFF();
        }
    }

    numberOfNewGW = 0;
}
/*
void StemNetStimulusBrain::takeRoleDecision(void) {
    STEM_ROLE_t oldRule, newRule;
    double actualResp;

    newRule = oldRule = db->getActualRule();

    actualResp = getResponseFuntion();

    if(dblrand() <= actualResp) {
        newRule = STEM_GATEWAY;
    }
    else {
        newRule = STEM_TRANSIT;
    }

    if (newRule != oldRule) {
        db->setActualRule(newRule);
    }

    // update gui
    if(ev.isGUI()) {
        char buf[40];
        sprintf(buf, "RF: %.2f", actualResp);
        this->getParentModule()->getParentModule()->getDisplayString().setTagArg("t", 0, buf);
    }
}
*/

double StemNetStimulusBrain::calculateResponseFunction(double stim) {
    double sm, th, ris;

    sm = pow(stim, responseFunctionExponent);
    th = pow((1.0 - calculateActualThreshold()), responseFunctionExponent);

    if (th == 0) {
        ris = 1;
    }
    else {
        ris = sm / (sm + th);
        ris = pow(ris, totalResponseFunctionExponent);
    }

    return ris;
}

double StemNetStimulusBrain::calculateActualStimulus(void) {
    double activeStimulus = alpha * (1.0 - db->getEnergyFactor()) + beta * (db->getCongestionFactorCell());

    return activeStimulus;
}

double StemNetStimulusBrain::calculateActualThreshold(void) {
    double alpha_fact, beta_fact;
    double e_max, d_max;
    double activeThreshold;
    std::list<Stimulus>::iterator it_list;

    // calculating the max values
    e_max = db->getMaxEnergyFactor();
    d_max = db->getMaxGatewayDistance();

    alpha_fact = beta_fact = 0;

    if (e_max > 0) {
        alpha_fact = alpha * (db->getEnergyFactor() / (e_max));
    }
    if (d_max > 0) {
        beta_fact = beta * (db->getAverageGatewayDistance() / (d_max));
    }

    activeThreshold = alpha_fact + beta_fact;

    return activeThreshold;
/*    double gamma_fact, delta_fact, eta_fact;
    double e_max, cwifi_max, d_max;
    double activeThreshold;
    std::list<Stimulus>::iterator it_list;

    // calculating the max values
    e_max = db->getMaxEnergyFactor();
    cwifi_max = db->getMaxCongestionFactorWifi();
    d_max = db->getMaxGatewayDistance();

    gamma_fact = delta_fact = eta_fact = 0;

    if (e_max > 0) {
        gamma_fact = gamma * (db->getEnergyFactor() / (e_max));
    }
    if (cwifi_max > 0) {
        delta_fact = delta * (db->getCongestionFactorWifi() / (cwifi_max));
    }
    if (d_max > 0) {
        eta_fact = eta * (db->getAverageGatewayDistance() / (d_max));
    }

    activeThreshold = gamma_fact + delta_fact + eta_fact;

    return activeThreshold;*/

}

double StemNetStimulusBrain::getResponseFuntion() {
    double ris;
    double sm, tm, sm_exp, tm_exp;

    //sm = calculateActualStimulus();
    //sm = db->getAverageNeighStimulus();
    sm = db->getMaxNeighStimulus();
    tm = calculateActualThreshold();

    sm_exp = pow(sm, responseFunctionExponent);
    tm_exp = pow(tm, responseFunctionExponent);

    if(tm == 1) {
        ris = 1;
    }
    else if(sm == 0) {
        ris = 0;
    }
    else {
        ris = (sm_exp) / ( (sm_exp) + (1 - tm_exp) );
    }

    return ris;
}

void StemNetStimulusBrain::broadcastPacket_1hop(cPacket *pkt) {
    std::list<IPvXAddress> ip_list;
    ip_list.clear();

    //looking for all 1-hop neighbors
    db->getNeighIPlist(ip_list);

    for(std::list<IPvXAddress>::iterator it_ip = ip_list.begin(); it_ip != ip_list.end(); it_ip++) {
        //IPv4Address broadcastAddr = IPv4Address(10,0,0,6);
        //socketStimulus.sendTo(payload, broadcastAddr, destPortStim);
        //socketStimulus.sendTo(payload, IPv4Address::ALLONES_ADDRESS, destPortStim);
        socketStimulus.sendTo(pkt->dup(), *it_ip, destPortStim);
    }
}

void StemNetStimulusBrain::generateStimulus(void) {
    if((db->getActualRule() == STEM_GATEWAY) && (maxStimulusStep > 0) && (protocolType == STEMNET)) {

        double actualStimulus = calculateActualStimulus();
        int msg_size = sizeof(double) + 2*sizeof(int) + 4;
        StimulusMSG *payload = new StimulusMSG("GatewayStimulus");

        payload->setStimulus_val(actualStimulus);
        payload->setEnergyFator(db->getEnergyFactor());
        payload->setCongestionFactor(db->getCongestionFactorCell());

        payload->setIp_src(myIP);
        payload->setMsg_id(forwardable_id);
        forwardable_id = forwardable_id + 1;
        payload->setTtl(maxStimulusStep);
        payload->setNHopCount(0);

        payload->setByteLength(msg_size);
        payload->setKind(UDP_I_DATA);

        EV << simTime() << " " << myIP << " Forward counter (generateStimulus)= " << forwardable_id << endl;

        //emulate the broadcast message 1-hop
        //broadcastPacket_1hop(payload);
        //delete(payload);

        socketStimulus.sendTo(payload, IPv4Address::ALLONES_ADDRESS, destPortStim);
        //socketStimulus.sendTo(payload, IPv4Address("127.255.255.255"), destPortStim);
    }
}

void StemNetStimulusBrain::generateGatewayON(IPvXAddress addr, bool first) {
    if (db->getActualRule() == STEM_GATEWAY) {
        generateGateway(true, addr, first);
    }
}

void StemNetStimulusBrain::generateGatewayOFF(void) {
    generateGateway(false, myIP, false);
}

void StemNetStimulusBrain::generateGateway(bool gwON, IPvXAddress addr, bool firstGW_ON) {

    if(sendGatewayMsg) {
        int msg_size = 5*sizeof(int) + sizeof(double);
        BasicBattery *bb = check_and_cast<BasicBattery *>(this->getParentModule()->getParentModule()->getSubmodule("battery"));
        GatewayMSG *payload = new GatewayMSG("GatewayAnnouncement");

        payload->setGatewayON(gwON);
        payload->setFirstON(firstGW_ON);
        payload->setStimulusSource(addr);
        payload->setEnergy(db->getEnergyFactor());
        payload->setEnergyResidual(bb->GetEnergy());

        payload->setIp_src(myIP);
        payload->setMsg_id(forwardable_id);
        forwardable_id = forwardable_id + 1;
        payload->setTtl(-1);    //FLOOD MSG
        payload->setNHopCount(0);

        EV << simTime() << " " << myIP << " Forward counter (generateGateway) = " << forwardable_id << endl;

        payload->setByteLength(msg_size);
        payload->setKind(UDP_I_DATA);

        socketStimulus.sendTo(payload, IPv4Address::ALLONES_ADDRESS, destPortStim);
    }
}

bool StemNetStimulusBrain::isDuplicated(StimulusMSG * smsg) {
    bool ris = false;
    std::list<Sent_Stimulus>::iterator it_dup;

    // looking if for the specified addres i've sent a pkt with a lower id
    for(it_dup = list_pkt_sent.begin(); it_dup != list_pkt_sent.end(); it_dup++) {
        if ( (it_dup->ip == smsg->getIp_src()) && (smsg->getMsg_id() <= it_dup->last_id_sent) ) {
            ris = true;
            break;
        }
    }

    return ris;
}

bool StemNetStimulusBrain::checkAlreaySeenAndForwardMessage(ForwardableMSG *forwardMsg) {

    // check if already forwarded
    bool ris = false;

    if (    (   (forwardMap.count(forwardMsg->getIp_src()) == 0) ||
                (forwardMap[forwardMsg->getIp_src()].last_id_seen < forwardMsg->getMsg_id()) ||
                (       (forwardMap[forwardMsg->getIp_src()].last_id_seen == forwardMsg->getMsg_id()) &&
                        (forwardMap[forwardMsg->getIp_src()].nHop > forwardMsg->getNHopCount()))) &&
            (forwardMsg->getIp_src() != myIP) ) {

        Forward_Map newMap;

        int actualTTL = forwardMsg->getTtl();

        newMap.last_id_seen = forwardMsg->getMsg_id();
        newMap.nHop = forwardMsg->getNHopCount();
        forwardMap[forwardMsg->getIp_src()] = newMap;

        EV << "FORWARDING msg " << forwardMsg->getName() << ". Hop count: " << forwardMsg->getNHopCount() << endl;

        // tell the program to analyze the packet
        ris = true;

        forwardMsg->setNHopCount(forwardMsg->getNHopCount() + 1);

        // check if the packet must be forwarded
        if ((actualTTL > 1) || (actualTTL < 0)) {

            if (actualTTL > 1) {
                forwardMsg->setTtl(actualTTL - 1);
            }

            queueForwardMsg.insert(forwardMsg->dup());
            if(dblrand() < 0.25) {
                // send the message 1.25 times
                queueForwardMsg.insert(forwardMsg->dup());
            }

            if(!msgForwardDelay->isScheduled()) {
                scheduleAt(simTime() + (dblrand() * maxForwardDelay), msgForwardDelay);
            }
        }
    }

    return ris;
    /*
    if ( ((actualTTL > 1) || (actualTTL < 0)) && (forwardMsg->getIp_src() != myIP) ) {
        if (    (forwardMap.count(forwardMsg->getIp_src()) == 0) ||
                (forwardMap[forwardMsg->getIp_src()] < forwardMsg->getMsg_id()) ){

            if (actualTTL > 1) {
                forwardMsg->setTtl(actualTTL - 1);
            }

            forwardMap[forwardMsg->getIp_src()] = forwardMsg->getMsg_id();

            queueForwardMsg.insert(forwardMsg->dup());

            if(!msgForwardDelay->isScheduled()) {
                scheduleAt(simTime() + (dblrand() * maxForwardDelay), msgForwardDelay);
            }
        }
    }
    */
}

void StemNetStimulusBrain::processPacket(cPacket *pk)
{
    if(strcmp(pk->getName(), "GatewayStimulus") == 0) {
        StimulusMSG *stimMsg = check_and_cast<StimulusMSG *>(pk);

        EV << myIP << " STIMULUS received by: " << stimMsg->getIp_src() << endl;

        if (stimMsg->getIp_src() != myIP) {
            if (checkAlreaySeenAndForwardMessage(stimMsg)) {

                // send directly to the DB
                db->updateFromStimulus(stimMsg->getIp_src(), stimMsg->getStimulus_val(), stimMsg->getEnergyFator(), stimMsg->getCongestionFactor(), stimMsg->getNHopCount());

                reactToStimulus(stimMsg);
            }
        }

        delete pk;
    }
    else if(strcmp(pk->getName(), "GatewayAnnouncement") == 0) {
        GatewayMSG *gwMsg = check_and_cast<GatewayMSG *>(pk);

        EV << myIP << " 'GATEWAY " << (gwMsg->getGatewayON() ? "ON" : "OFF") << "' message received by: " << gwMsg->getIp_src() << endl;

        /*{
            char buff_tmp[128];
            snprintf(buff_tmp, sizeof(buff_tmp), "%s:%s:%d", simTime().str().c_str(), gwMsg->getIp_src().str().c_str(), gwMsg->getGatewayON());
            this->getParentModule()->getParentModule()->getDisplayString().setTagArg("tt", 0, buff_tmp);
        }*/

        if (gwMsg->getIp_src() != myIP) {
            if (checkAlreaySeenAndForwardMessage(gwMsg)) {

                // send directly to the DB
                db->updateFromGatewayMsg(gwMsg->getIp_src(), gwMsg->getGatewayON(), gwMsg->getEnergy(), gwMsg->getEnergyResidual(), gwMsg->getNHopCount());

                reactToGateway(gwMsg);
            }
        }

        delete pk;

    }
    else if(strncmp(pk->getName(), "UDP_STEM_Data", 13) == 0) {
        // somebody sent me a packet. Am I a gateway?
        EV << "Packet received: " << pk->getName() << endl;

        if(db->getActualRule() == STEM_GATEWAY) {

            EV << "I'm a gateway. Sending to 3G module..." << endl;

            emit(rcvdPkSignal, pk);
            EV << "Received packet: " << UDPSocket::getReceivedPacketInfo(pk) << endl;
            send(pk, dummy3GOut);
            numReceived++;

        }
        else {
            // I'm no more a gateway.. searching for another...
            IPvXAddress destAddr = chooseGatewayAddr();

            if(destAddr == IPv4Address::LOOPBACK_ADDRESS) {
                // there are no gateway... change my behavior

                if((becomeGWifAlone) && (module3GPresent)) {

                    EV << "Changing the behavior to gateway because I don't know any other gateway" << endl;

                    changeRole(STEM_GATEWAY);
                    send(pk, dummy3GOut);
                }
                else {
                    delete(pk);
                }
            }
            else {
                EV << "Sending to the next hop: " << destAddr << endl;
                emit(sentPkSignal, pk);
                socket.sendTo(pk->dup(), destAddr, destPort);

                delete(pk);
            }
        }
    }
    else {
        delete pk;
    }
}

IPvXAddress StemNetStimulusBrain::chooseGatewayAddr(void) {
    IPvXAddress ris = db->getNearestGateway();

    //if (ris == IPv4Address::LOOPBACK_ADDRESS) {
    //    // there are no gateway... change my behavior
    //    db->setActualRule(STEM_GATEWAY);
    //}

    return ris;
}

void StemNetStimulusBrain::generateTraffic(void) {

    IPvXAddress destAddr;
    char msgName[64];
    sprintf(msgName, "UDP_STEM_Data-%d_%s", numSent, myIP.str().c_str());
    cPacket *payload = new cPacket(msgName);

    payload->setByteLength(trafficMessageLength);

    //byteGenerated->record(trafficMessageLength);

    countPktGenerated++;
    totalPktGenerated++;

    if (db->getActualRule() == STEM_GATEWAY) {
        //send direct to the 3G module
        send(payload, dummy3GOut);
    }
    else {
        destAddr = chooseGatewayAddr();

        EV << myIP << " Checking to send payload. Gateway chosen: " << destAddr << endl;

        if(destAddr == IPv4Address::LOOPBACK_ADDRESS) {
            // there are no gateway... change my behavior
            if((becomeGWifAlone) && (module3GPresent)) {
                changeRole(STEM_GATEWAY);

                generateGatewayON(IPv4Address::ALLONES_ADDRESS, true);    // mando un GATEWAY msg generico

                send(payload, dummy3GOut);
            }
            else {
                delete payload;
            }
        }
        else {
            emit(sentPkSignal, payload);
            EV << myIP << " Sending a payload message towards " << destAddr << endl;
            socket.sendTo(payload, destAddr, destPort);
        }
    }
    numSent++;
}

void StemNetStimulusBrain::handleWifiMsg(cMessage *msg) {

    if (msg->getKind() == UDP_I_DATA)
    {
        // process incoming packet
        processPacket(PK(msg));
    }
    else if (msg->getKind() == UDP_I_ERROR)
    {
        EV << "Ignoring UDP error report\n";
        delete msg;
    }
    else {
        EV << "Ignoring UDP message: " << msg->getFullName() << endl;;
        delete msg;
    }
}

void StemNetStimulusBrain::finish()
{
    UDPBasicApp::finish();

    makeFinalStats();
}

void StemNetStimulusBrain::reactToStimulus(StimulusMSG *stimMsg) {

    // react only if i have got the 3g module
    if(module3GPresent){

        double rf = calculateResponseFunction(stimMsg->getStimulus_val());
        double randVar = dblrand();

        if (randVar < rf) {     // RISPONDO allo stimolo!!!

            if (msgElectionDelay->isScheduled()) {
                // TODO
                // cosa faccio? per ora ignoro questo secondo stimolo, visto che sto già rispondendo
                // ad uno stimolo precedente
            }
            else {
                stimSrc = stimMsg->getIp_src();
                newlyElectedGW = 0;
                CcellSrcStimulus = stimMsg->getCongestionFactor();
                scheduleAt(simTime() + fabs((timeElectionPhase * (1.0 - rf))), msgElectionDelay);
            }
        }
    }
}

void StemNetStimulusBrain::reactToGateway(GatewayMSG *gwMsg) {
    if((gwMsg->getGatewayON()) && (gwMsg->getFirstON())) {

        if(gwMsg->getStimulusSource() != myIP) {
            switch(electionType) {
            case SINGLE:
            default:
                reactToGateway_SingleElection(gwMsg);
                break;

            case MULTIPLE:
                reactToGateway_MultipleElection(gwMsg);
                break;
            }

            numberOfNewGW++;
        }
        else {
            //TODO vedere il caso di risposta al proprio stimolo
        }
    }
    else {
        //TODO
    }
}

void StemNetStimulusBrain::reactToGateway_SingleElection(GatewayMSG *gwMsg) {

    if ((msgElectionDelay->isScheduled()) && (stimSrc == gwMsg->getStimulusSource()) ) {
        // Delete my chance to become a gateway
        cancelEvent(msgElectionDelay);
    }
}

void StemNetStimulusBrain::reactToGateway_MultipleElection(GatewayMSG *gwMsg) {

    if (msgElectionDelay->isScheduled()) {

        // controllo che stiamo rispondendo allo stesso stimolo
        if (stimSrc == gwMsg->getStimulusSource()) {
            double newStimulusVal, randVal, newRF;

            // new gateway elected
            newlyElectedGW++;

            newStimulusVal = (alpha * (1.0 - gwMsg->getEnergy())) + (beta * (CcellSrcStimulus / (newlyElectedGW + 1)));
            randVal = dblrand();
            newRF = calculateResponseFunction(newStimulusVal);

            simtime_t delta_e = msgElectionDelay->getArrivalTime() - simTime();

            // remove the old message
            cancelEvent(msgElectionDelay);

            if (randVal < newRF) { // passo ancora il threshold dello stimolo... reinserisco il self-msg col nuovo ritardo
                scheduleAt(simTime() + fabs((delta_e * (1.0 - newRF))), msgElectionDelay);
            }
        }
    }
}

void StemNetStimulusBrain::reactToDeferTimeExpired(void){
    // become a GATEWAY
    changeRole(STEM_GATEWAY);

    // send the GATEWAY msg
    generateGatewayON(stimSrc, true);
    stimSrc = IPv4Address::ALLONES_ADDRESS;
}

typedef struct {
    int appAddr;
    IPvXAddress ipAddr;
    StemNetStimulusBrain *ssb;
    KnowledgeRepository *kr;
} nn_struct_t;

typedef struct {
    //std::list<nn_struct_t> nodes;
    std::list<IPvXAddress> all_group;
} group_struct_t;

void StemNetStimulusBrain::chooseAndElectTheBestGW(void) {
    if(myApplicationAddress == 0) {
        //Faccio tutto io... sono DIO
        int nNodes;
        std::list<group_struct_t> group_l;
        std::list<group_struct_t> group_final;
        std::list<group_struct_t> group_final_final;

        group_l.clear();
        group_final.clear();
        group_final_final.clear();

        nNodes = this->getParentModule()->getParentModule()->getParentModule()->par("numHosts");
        for (int i = 0; i < nNodes; i++) {
            StemNetStimulusBrain *ssb = check_and_cast<StemNetStimulusBrain *>(this->getParentModule()->getParentModule()->getParentModule()->getSubmodule("host", i)->getSubmodule("udpApp", 0)->getSubmodule("brain"));
            KnowledgeRepository *kr = check_and_cast<KnowledgeRepository *>(this->getParentModule()->getParentModule()->getParentModule()->getSubmodule("host", i)->getSubmodule("udpApp", 0)->getSubmodule("db"));

            bool trovato = false;

            //cerco il nodo
            for (std::list<group_struct_t>::iterator it_g = group_l.begin(); it_g != group_l.end(); it_g++) {
                std::list<IPvXAddress>::iterator it_n;
                for (it_n = it_g->all_group.begin(); it_n != it_g->all_group.end(); it_n++) {
                    if (ssb->myIP == (*it_n)) {
                        //ci sono, aggiungo solo i miei conoscenti
                        for (std::map<IPvXAddress, Gateway_t>::iterator it_map = kr->gatewayMap.begin(); it_map != kr->gatewayMap.end(); it_map++){
                            it_g->all_group.push_back(it_map->second.ipSrc);
                        }

                        trovato = true;

                        break;
                    }
                }
                if(!trovato) {
                    // non ci sono, controllo i miei vicini
                    for (it_n = it_g->all_group.begin(); it_n != it_g->all_group.end(); it_n++) {
                        for (std::map<IPvXAddress, Gateway_t>::iterator it_map = kr->gatewayMap.begin(); it_map != kr->gatewayMap.end(); it_map++){
                            if(it_map->second.ipSrc == (*it_n)) {
                                for (std::map<IPvXAddress, Gateway_t>::iterator it_map = kr->gatewayMap.begin(); it_map != kr->gatewayMap.end(); it_map++){
                                    it_g->all_group.push_back(it_map->second.ipSrc);
                                }

                                it_g->all_group.push_back(ssb->myIP);
                                trovato = true;
                                break;
                            }
                        }
                        if (trovato){
                            break;
                        }
                    }
                }

                if(trovato){
                    break;
                }
            }

            if (!trovato) {
                group_struct_t newg;

                for (std::map<IPvXAddress, Gateway_t>::iterator it_map = kr->gatewayMap.begin(); it_map != kr->gatewayMap.end(); it_map++){
                    newg.all_group.push_back(it_map->second.ipSrc);
                }
                newg.all_group.push_back(ssb->myIP);

                group_l.push_back(newg);
            }
        }

        EV << "Printing first step groups" << endl;
        int jj = 0;
        for (std::list<group_struct_t>::iterator it_g = group_l.begin(); it_g != group_l.end(); it_g++) {
            EV << "Group " << jj++ << endl;
            for (std::list<IPvXAddress>::iterator it_n = it_g->all_group.begin(); it_n != it_g->all_group.end(); it_n++) {
                EV << *it_n << " ";
            }
            EV << endl;
        }
        EV << endl;

        //raggruppo i gruppi comuni
        for (std::list<group_struct_t>::iterator it_g = group_l.begin(); it_g != group_l.end(); it_g++) {
            bool trovato = false;
            for (std::list<group_struct_t>::iterator it_gf = group_final.begin(); it_gf != group_final.end(); it_gf++) {

                for (std::list<IPvXAddress>::iterator it_n = it_g->all_group.begin(); it_n != it_g->all_group.end(); it_n++) {
                    for (std::list<IPvXAddress>::iterator it_final = it_gf->all_group.begin(); it_final != it_gf->all_group.end(); it_final++) {
                        if(*it_n == * it_final) {
                            trovato = true;
                        }

                        if(trovato) break;
                    }
                    if(trovato) break;
                }

                if(trovato) {
                    //svuoto tutta la lista qui
                    for (std::list<IPvXAddress>::iterator it_n = it_g->all_group.begin(); it_n != it_g->all_group.end(); it_n++) {
                        it_gf->all_group.push_back(*it_n);
                    }

                    break;
                }
            }

            if(!trovato) {
                //creo una nuova lista
                group_struct_t newl;

                for (std::list<IPvXAddress>::iterator it_n = it_g->all_group.begin(); it_n != it_g->all_group.end(); it_n++) {
                    newl.all_group.push_back(*it_n);
                }

                group_final.push_back(newl);
            }
        }


        EV << "Printing final groups" << endl;
        int jf = 0;
        for (std::list<group_struct_t>::iterator it_gf = group_final.begin(); it_gf != group_final.end(); it_gf++) {
            EV << "Group " << jf++ << endl;
            for (std::list<IPvXAddress>::iterator it_final = it_gf->all_group.begin(); it_final != it_gf->all_group.end(); it_final++) {
                EV << *it_final << " ";
            }
            EV << endl;
        }
        EV << endl;

        // elimino le ridondanze
        for (std::list<group_struct_t>::iterator it_gf = group_final.begin(); it_gf != group_final.end(); it_gf++) {
            group_struct_t newS;

            newS.all_group.clear();

            for (std::list<IPvXAddress>::iterator it_final = it_gf->all_group.begin(); it_final != it_gf->all_group.end(); it_final++) {
                bool already = false;

                for (std::list<IPvXAddress>::iterator it_tmp = newS.all_group.begin(); it_tmp != newS.all_group.end(); it_tmp++) {
                    if(*it_tmp == *it_final) {
                        already = true;
                    }
                }

                if(!already){
                    newS.all_group.push_back(*it_final);
                }
            }

            group_final_final.push_back(newS);
        }

        EV << "Printing final final groups" << endl;
        int jff = 0;
        for (std::list<group_struct_t>::iterator it_gf = group_final_final.begin(); it_gf != group_final_final.end(); it_gf++) {
            EV << "Group " << jff++ << endl;
            for (std::list<IPvXAddress>::iterator it_final = it_gf->all_group.begin(); it_final != it_gf->all_group.end(); it_final++) {
                EV << *it_final << " ";
            }
            EV << endl;
        }
        EV << endl;

        for (std::list<group_struct_t>::iterator it_gf = group_final_final.begin(); it_gf != group_final_final.end(); it_gf++) {
            //scelgo il migliore per ogni nodo
            IPvXAddress ipAddrBest;
            StemNetStimulusBrain *ssb_best = NULL;
            double bestEnergy = 0;

            for (std::list<IPvXAddress>::iterator it_final = it_gf->all_group.begin(); it_final != it_gf->all_group.end(); it_final++) {
                StemNetStimulusBrain *ssb = NULL;
                KnowledgeRepository *kr = NULL;
                BasicBattery *bb = NULL;
                int i;

                // prima cerco il modulo con l'ip
                for (i = 0; i < nNodes; i++) {
                    StemNetStimulusBrain *ssb_t = check_and_cast<StemNetStimulusBrain *>(this->getParentModule()->getParentModule()->getParentModule()->getSubmodule("host", i)->getSubmodule("udpApp", 0)->getSubmodule("brain"));
                    KnowledgeRepository *kr_t = check_and_cast<KnowledgeRepository *>(this->getParentModule()->getParentModule()->getParentModule()->getSubmodule("host", i)->getSubmodule("udpApp", 0)->getSubmodule("db"));
                    BasicBattery *bb_t = check_and_cast<BasicBattery *>(this->getParentModule()->getParentModule()->getParentModule()->getSubmodule("host", i)->getSubmodule("battery"));

                    if (ssb_t->myIP == *it_final){
                        ssb = ssb_t;
                        kr = kr_t;
                        bb = bb_t;

                        break;
                    }
                }

                if ((ssb != NULL) && (kr != NULL) && (bb != NULL)) {
                    if((bb->GetEnergy() > bestEnergy) && (ssb->getModule3GPresent())) {
                        bestEnergy = bb->GetEnergy();
                        ssb_best = ssb;
                        ipAddrBest = ssb->myIP;
                    }
                }
            }

            if(ssb_best != NULL) {
                for (std::list<IPvXAddress>::iterator it_final = it_gf->all_group.begin(); it_final != it_gf->all_group.end(); it_final++) {
                    StemNetStimulusBrain *ssb = NULL;
                    KnowledgeRepository *kr = NULL;
                    BasicBattery *bb = NULL;
                    int i;

                    // prima cerco il modulo con l'ip
                    for (i = 0; i < nNodes; i++) {
                        StemNetStimulusBrain *ssb_t = check_and_cast<StemNetStimulusBrain *>(this->getParentModule()->getParentModule()->getParentModule()->getSubmodule("host", i)->getSubmodule("udpApp", 0)->getSubmodule("brain"));
                        KnowledgeRepository *kr_t = check_and_cast<KnowledgeRepository *>(this->getParentModule()->getParentModule()->getParentModule()->getSubmodule("host", i)->getSubmodule("udpApp", 0)->getSubmodule("db"));
                        BasicBattery *bb_t = check_and_cast<BasicBattery *>(this->getParentModule()->getParentModule()->getParentModule()->getSubmodule("host", i)->getSubmodule("battery"));

                        if (ssb_t->myIP == *it_final){
                            ssb = ssb_t;
                            kr = kr_t;
                            bb = bb_t;

                            break;
                        }
                    }

                    if ((ssb != NULL) && (kr != NULL) && (bb != NULL)) {
                        if(ssb->myIP == ssb_best->myIP) {
                            ssb->changeRole(STEM_GATEWAY);
                        }
                        else {
                            ssb->changeRole(STEM_TRANSIT);
                        }
                    }
                }
            }
        }
    }
}

void StemNetStimulusBrain::chooseAndElectTheBestGW_old(void) {
    BasicBattery *bb = check_and_cast<BasicBattery *>(this->getParentModule()->getParentModule()->getSubmodule("battery"));
    std::map<IPvXAddress, Gateway_t>::iterator it_map;
    bool imTheBest = true;
    double bestEnergy = 0;
    double myEnergyFactor = bb->GetEnergy(); //db->getEnergyFactor();

    EV << myIP << " My energy: " << myEnergyFactor << endl;

    for(it_map = db->gatewayMap.begin(); it_map != db->gatewayMap.end(); it_map++) {

        EV << it_map->second.ipSrc << " energy: " << it_map->second.energyFator << "; ";

        //if(it_map->second.energyFator > myEnergyFactor) {
        if(it_map->second.energyResidual > myEnergyFactor) {
            imTheBest = false;

            if(bestEnergy < it_map->second.energyResidual) {
                bestEnergy = it_map->second.energyResidual;
            }
        }
    }

    EV << myIP << " imTheBest?: " << imTheBest << endl;

    if (imTheBest) {
        changeRole(STEM_GATEWAY);
    }
    else {

        if ((abs(bestEnergy - myEnergyFactor) < (bb->GetInitialEnergy() / 1000)) && (dblrand() < 0.1)) {
            changeRole(STEM_GATEWAY);
        }
        else {
            changeRole(STEM_TRANSIT);
        }
    }
}

void StemNetStimulusBrain::probabilisticBecomeGW(void) {
    double rand = dblrand();

    if ((module3GPresent) && (rand < probabilisticValue)) {
        changeRole(STEM_GATEWAY);
    }
    else {
        changeRole(STEM_TRANSIT);
    }
}

void StemNetStimulusBrain::distanceBecomeGW(void) {

    if ((module3GPresent) && ((db->getHopNearestGateway() < 0) || (db->getHopNearestGateway() > distanceToGW))) {
        changeRole(STEM_GATEWAY);
    }
    else {
        changeRole(STEM_TRANSIT);
    }
}

void StemNetStimulusBrain::makePeriodocStats(void) {
    BasicBattery *bb = check_and_cast<BasicBattery *>(this->getParentModule()->getParentModule()->getSubmodule("battery"));
    STEM_ROLE_t actRole = db->getRealActualRule();

    EV << "Make periodic stats " << endl;

    pktGenerated->record(countPktGenerated);
    countPktGenerated = 0;

    //myActualRole->record(db->getActualRule()); // TODO fare meglio... controllare se sono TRANSIT o STUB
    myActualRole->record(actRole);
    switch(actRole) {
    case STEM_GATEWAY:
        isGatewayRole->record(1);
        gatewayList.push_back(std::make_pair<simtime_t, int>(simTime(), 1));
        transitList.push_back(std::make_pair<simtime_t, int>(simTime(), 0));
        stubList.push_back(std::make_pair<simtime_t, int>(simTime(), 0));
        break;
    case STEM_TRANSIT:
        isTransitRole->record(1);
        gatewayList.push_back(std::make_pair<simtime_t, int>(simTime(), 0));
        transitList.push_back(std::make_pair<simtime_t, int>(simTime(), 1));
        stubList.push_back(std::make_pair<simtime_t, int>(simTime(), 0));
        break;
    case STEM_STUB:
        isStubRole->record(1);
        gatewayList.push_back(std::make_pair<simtime_t, int>(simTime(), 0));
        transitList.push_back(std::make_pair<simtime_t, int>(simTime(), 0));
        stubList.push_back(std::make_pair<simtime_t, int>(simTime(), 1));
        break;
    default:
        break;
    }

    actualEnergyVal->record(bb->GetEnergy());
}

void StemNetStimulusBrain::makeFinalStats(void) {
    simtime_t nowT = simTime();

    recordScalar("Total Packets Generated", totalPktGenerated);

    if (myApplicationAddress == 0) {
        recordScalar("Final Arrival Time", nowT);
    }


    /*for (std::list<std::pair<simtime_t, int> >::iterator it = gatewayList.begin(); it != gatewayList.end(); it++) {
        if(it->first >= (nowT - FINAL_STAT_MEAN_PERIOD)) {
            countGW++;
            sumGW += it->second;
        }
    }
    if(countGW > 0) totGW = ((double) sumGW) / ((double) countGW);
    recordScalar("Average Final Is Gateway", totGW);

    for (std::list<std::pair<simtime_t, int> >::iterator it = transitList.begin(); it != transitList.end(); it++) {
        if(it->first >= (nowT - FINAL_STAT_MEAN_PERIOD)) {
            countTR++;
            sumTR += it->second;
        }
    }
    if(countTR > 0) totTR = ((double) sumTR) / ((double) countTR);
    recordScalar("Average Final Is Transit", totTR);

    for (std::list<std::pair<simtime_t, int> >::iterator it = stubList.begin(); it != stubList.end(); it++) {
        if(it->first >= (nowT - FINAL_STAT_MEAN_PERIOD)) {
            countST++;
            sumST += it->second;
        }
    }
    if(countST > 0) totST = ((double) sumST) / ((double) countST);
    recordScalar("Average Final Is Stub", totST);*/


    recordScalar("Average Final Is Gateway", getAverageFinalRole(STEM_GATEWAY, FINAL_STAT_MEAN_PERIOD));
    recordScalar("Average Final Is Transit", getAverageFinalRole(STEM_TRANSIT, FINAL_STAT_MEAN_PERIOD));
    recordScalar("Average Final Is Stub", getAverageFinalRole(STEM_STUB, FINAL_STAT_MEAN_PERIOD));

    recordScalar("Average Final Is Gateway AllTime", getAverageFinalRole(STEM_GATEWAY, nowT));
    recordScalar("Average Final Is Transit AllTime", getAverageFinalRole(STEM_TRANSIT, nowT));
    recordScalar("Average Final Is Stub AllTime", getAverageFinalRole(STEM_STUB, nowT));

    recordScalar("Average Final Is Gateway HalfTime", getAverageFinalRole(STEM_GATEWAY, nowT/2.0));
    recordScalar("Average Final Is Transit HalfTime", getAverageFinalRole(STEM_TRANSIT, nowT/2.0));
    recordScalar("Average Final Is Stub HalfTime", getAverageFinalRole(STEM_STUB, nowT/2.0));

    if(myApplicationAddress == 0) {
        int nNodes = this->getParentModule()->getParentModule()->getParentModule()->par("numHosts");
        double sumGW = 0;
        double sumTR = 0;
        double sumST = 0;
        double sumGW_all = 0;
        double sumTR_all = 0;
        double sumST_all = 0;
        double sumGW_half = 0;
        double sumTR_half = 0;
        double sumST_half = 0;

        for (int i = 0; i < nNodes; i++) {
            StemNetStimulusBrain *br = check_and_cast<StemNetStimulusBrain *>(this->getParentModule()->getParentModule()->getParentModule()->getSubmodule("host", i)->getSubmodule("udpApp", 0)->getSubmodule("brain"));

            sumGW += br->getAverageFinalRole(STEM_GATEWAY, FINAL_STAT_MEAN_PERIOD);
            sumTR += br->getAverageFinalRole(STEM_TRANSIT, FINAL_STAT_MEAN_PERIOD);
            sumST += br->getAverageFinalRole(STEM_STUB, FINAL_STAT_MEAN_PERIOD);

            sumGW_all += br->getAverageFinalRole(STEM_GATEWAY, nowT);
            sumTR_all += br->getAverageFinalRole(STEM_TRANSIT, nowT);
            sumST_all += br->getAverageFinalRole(STEM_STUB, nowT);

            sumGW_half += br->getAverageFinalRole(STEM_GATEWAY, nowT/2.0);
            sumTR_half += br->getAverageFinalRole(STEM_TRANSIT, nowT/2.0);
            sumST_half += br->getAverageFinalRole(STEM_STUB, nowT/2.0);
        }

        recordScalar("Average Total Final Is Gateway", sumGW);
        recordScalar("Average Total Final Is Transit", sumTR);
        recordScalar("Average Total Final Is Stub", sumST);
        recordScalar("Average Total Final Is Gateway percent", ((sumGW * 100.0) / ((double) nNodes)));
        recordScalar("Average Total Final Is Transit percent", ((sumTR * 100.0) / ((double) nNodes)));
        recordScalar("Average Total Final Is Stub percent", ((sumST * 100.0) / ((double) nNodes)));

        recordScalar("Average Total Final Is Gateway AllTime", sumGW_all);
        recordScalar("Average Total Final Is Transit AllTime", sumTR_all);
        recordScalar("Average Total Final Is Stub AllTime", sumST_all);
        recordScalar("Average Total Final Is Gateway percent AllTime", ((sumGW_all * 100.0) / ((double) nNodes)));
        recordScalar("Average Total Final Is Transit percent AllTime", ((sumTR_all * 100.0) / ((double) nNodes)));
        recordScalar("Average Total Final Is Stub percent AllTime", ((sumST_all * 100.0) / ((double) nNodes)));

        recordScalar("Average Total Final Is Gateway HalfTime", sumGW_half);
        recordScalar("Average Total Final Is Transit HalfTime", sumTR_half);
        recordScalar("Average Total Final Is Stub HalfTime", sumST_half);
        recordScalar("Average Total Final Is Gateway percent HalfTime", ((sumGW_half * 100.0) / ((double) nNodes)));
        recordScalar("Average Total Final Is Transit percent HalfTime", ((sumTR_half * 100.0) / ((double) nNodes)));
        recordScalar("Average Total Final Is Stub percent HalfTime", ((sumST_half * 100.0) / ((double) nNodes)));
    }
}

double StemNetStimulusBrain::getAverageFinalRole(STEM_ROLE_t role, simtime_t time) {
    std::list<std::pair<simtime_t, int> >::iterator begin_it, end_it, it;
    int count = 0;
    double tot = 0.0;
    int sum = 0;
    simtime_t nowT = simTime();

    switch (role) {
    case STEM_GATEWAY:
        begin_it = gatewayList.begin();
        end_it = gatewayList.end();
        break;
    case STEM_TRANSIT:
        begin_it = transitList.begin();
        end_it = transitList.end();
        break;
    case STEM_STUB:
        begin_it = stubList.begin();
        end_it = stubList.end();
        break;
    }

    for (std::list<std::pair<simtime_t, int> >::iterator it = begin_it; it != end_it; it++) {
        //if ((alltime) || (it->first >= (nowT - FINAL_STAT_MEAN_PERIOD)) ) {
        if ( it->first >= (nowT - time) ) {
            count++;
            sum += it->second;
        }
    }

    if(count > 0) {
        tot = ((double) sum) / ((double) count);
    }

    return tot;
}
